#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""This is a simple python script to fill your mp3-player with randomly selected albums
	usage: ./tagger
	options:
		-? Instructions
		-s "path to your library" Scan the mp3-folder for files
	 	-a copy the folders automatically (no confirmation needed)
	 	-l create HTML output
"""

import os, sys, random, pickle, statvfs
from tagger import *
from functions import *

import re		# regular expressions
import urllib	# http requests
import shutil	# to copy files

import settings # personal settings my be defined in settings.py (cover finder features)

# set the Http-Agent for Http requests
class AppURLopener(urllib.FancyURLopener):
    version = "Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101"

urllib._urlopener = AppURLopener()

class TAlbum():
	def __init__(self, artist, album, path):
		self.artist	= artist
		self.album 	= album
		self.path 	= path
		self.size 	= 0
		self.tracks = []		#stores all the files in one album
		self.count 	= 0
		self.neveragain = False
	def __del__(self):
		del self.artist
		del self.album
		del self.tracks
		del self.size
		del self.path
	def append(self, song):
		self.tracks.append(song)
	def getsize(self):
		"""Returns the size of an album"""
		if self.size == 0:
			for track in self.tracks:
				try: self.size += os.path.getsize(track)
				except OSError:
					#pass # if an album is not found then be silent
					print "Error: file "+ track +" not found"

		try: # add the size of the cover
			self.size += os.path.getsize(os.path.join(self.path, settings.COVER_IMAGE_NAME))
		except OSError:
			pass # if a cover is not found then be silent

		return self.size

	def savem3u(self,filename):
		"""saves the list of tracks as m3u-list."""
		try:
			m3u = open(filename, "w")
			for t in self.tracks:
				temp = ReplaceBadChars(t)
				m3u.write(os.path.basename(temp) + '\n')
			m3u.close()
			print filename, "written"
		except:
			return

	def findCover(self, targetpath):
		"""Image search for the cover"""
		coverpath 	= os.path.join(self.path, settings.COVER_IMAGE_NAME);
		target 		= ReplaceBadChars(os.path.join(targetpath, settings.COVER_IMAGE_NAME))

		if not os.path.exists(coverpath): # search for a cover
			cover_found = False
			if os.path.exists(settings.COVER_LOCAL_PATH): # local search
				print "searching for cover in", settings.COVER_LOCAL_PATH

				cmd = 'find %s -name "*%s*%s*.jpg"' % (settings.COVER_LOCAL_PATH,self.artist.encode('utf-8'),self.album.encode('utf-8'))
				print cmd
				res = os.popen(cmd).readlines()
				print res

				if len(res) > 0:
					shutil.copyfile(res[0].strip(), coverpath) # copy the first file found
					cover_found = True

			if not cover_found: # do web request
				if not settings.COVER_QUERY_STRING: return; # Query String was empty

				print "Searchin' cover on ", settings.COVER_QUERY_STRING

				search =  urllib.quote_plus(settings.COVER_QUERY_STRING +'%s+%s' % (self.artist.encode('utf-8'), self.album.encode('utf-8')), '/:?=')
				error = False
				try:
					data = urllib.urlopen(search).read() 	# http request
					#print data
				except:
					print "Error, wrong encoded url ?"
					error = True

				if error: return

				p = re.compile(settings.COVER_REGULAR_EXPR)	#find only image urls
				files = p.findall(data)
				#print files
				if len(files) > 0:
					for i in files:
						error = False
						#img = i+'.jpg'
						img = i
						print "Image found: ", img
						try:
							data = urllib.urlopen(img).read() 	# http request
						except:
							error = True
						if error: continue
						file = open(coverpath, "w");
						file.write(data)
						file.close()
						if not error: break
				print


		if os.path.exists(coverpath): # maybe it exists now
			try:
				shutil.copyfile(coverpath, target)
			except:
				print "could not copy cover.jpg"

		return

class TAlbumList():
	def __init__(self, path):
		self.path = path

		self.db = []
		self.sel = 0
	def __del__(self):
		del self.db
		del self.path
		del self.sel
	def append(self, artist, album, song):
		found = False
		index = 0

		path = os.path.dirname(song)

		for i in range(len(self.db)):									#check for existing albums
			a = self.db[i]
			#if (artist == a.artist) and (album == a.album) and (path == a.path):
			if (album == a.album) and (path == a.path):
				if (artist != a.artist):
					a.artist = u'Various Artists'
				found = True
				index = i
				del a
				break
			del a

		if not found:													#append new album
			temp = TAlbum(artist,album,path)
			self.db.append(temp)
			index = len(self.db) - 1
			del temp

		album = self.db[index]
		if not song in album.tracks:
			album.append(song)

	def remove(self, album, verbose=False):
		if verbose: print "Removing %s - %s" %(album.artist.encode('utf-8'),album.album.encode('utf-8'))
		self.db.remove(album)

	def GetAlbumCount(self, freespace):
		"""Returns the number of albums wich are allowed to be selected"""
		cnt = 0
		for a in List.db:
			if not a.neveragain and a.size < freespace:
				cnt += 1
		return cnt

	def GetSelections(self):
		"""Returns the number of selections made by the script"""
		cnt = 0
		for a in List.db:
			if not a.neveragain:
				cnt += a.count
		self.sel=cnt
		return self.sel

	def GetAllAlbumCount(self):
		"""Returns the number of albums in the list"""
		return len(self.db)

	def GetAllTracksCount(self):
		"""Returns te number of albums"""
		cnt = 0
		for a in List.db:
			cnt += len(a.tracks)
		return cnt

	def info(self):
		print 76* "-"
		print "Info for music db in %s" % (self.path)
		print "# albums\t:\t%d" % (self.GetAllAlbumCount())
		print "# songs\t\t:\t%d" % (self.GetAllTracksCount())
		print 76* "-"

def dumpList(List):
	"""Saves The list of albums to a file"""
	file=open("db.pickle", "w")
	pickle.dump(List, file)
	file.close()

def dumpObj(Obj, Filename):
	"""Saves lists to a textfile"""
	file=open(Filename, "w")
	for i in Obj:
		file.write(i + '\n')
	file.close()

def ScanDB(List, verbose=False):
	print "Scanning", List.path, "for mp3-files."

	no_tag 		= []
	no_artist 	= []
	no_album 	= []
	no_track 	= []

	for root, dirs, files in os.walk(List.path):
		for f in files:
			if f.endswith('.MP3') or f.endswith('.mp3'):

				notag = False

				song = os.path.join(root, f)
				dir = os.path.dirname(song)
				dirlist = dir.split('/')
				#if verbose: print "checking:", song
				artst, album, track, tag_exists = GetArtAlb(song)

				if (artst != None) and (album != None):
					#if verbose: print "Appending %s - %s:" % (artst.encode('utf-8'),album.encode('utf-8'))
					List.append(artst, album, song)

				if not tag_exists:
					if verbose: print "Warning:",song, "has no tag"
					no_tag.append(song)
				else:
					if (artst == None) or (artst == ''):
						if verbose: print "Warning:",song, "has no proper artist tag"
						no_artist.append(song)
					if (album == None) or (album == ''):
						if verbose: print "Warning:",song, "has no proper album tag"
						no_album.append(song)
					if (track == None) or (track == ''):
						if verbose: print "Warning:",song, "has no proper track tag"
						no_track.append(song)

	#Save the data
	dumpList(List)
	dumpObj(no_tag, 'no_tag.log')
	dumpObj(no_artist, 'no_artist.log')
	dumpObj(no_album,  'no_album.log')
	dumpObj(no_track,  'no_track.log')

def CleanDB(List, verbose=False):
	"""Cleans database from non existing files and albums"""
	print "Cleaning up db ... "
	for i in range(len(List.db)-1,-1,-1):
		a = List.db[i]
		album_still_exists = False
		for t in a.tracks:
			track_exists = os.path.exists(t)
			if track_exists:
				album_still_exists = True
			else:
				if verbose:	print "%s does not exist." % (t)
				a.tracks.remove(t)
			del track_exists
		if not album_still_exists:
			List.remove(a, verbose)

	#Save the data
	dumpList(List)
	print "... ready."

def RandomAlbum(List, freespace):
	"""Selects one album randomly"""
	ok = False
	count = 0
	try:
		threshhold = 1./float(List.GetAlbumCount(freespace))
	except: # if free space is exactly zero
		return None

	while not ok:
		a = random.choice(List.db)
		albumsize = a.getsize()

		count += 1

		if (albumsize == 0):
			List.remove(a)
			continue

		if count == len(List.db)-1: return None
		if a.neveragain: continue
		elif albumsize >= freespace: continue
		try:
			test = float(a.count)/float(List.GetSelections())
		except ZeroDivisionError:
			test = 0.

		if  test <= threshhold:
			a.count += 1
			ok = True
	return a


def FolderSize(folder):
	folder_size = 0
	for (path, dirs, files) in os.walk(folder):
		for file in files:
			filename = os.path.join(path, file)
			folder_size += os.path.getsize(filename)
	print "Folder = %0.1f MB" % (folder_size/(1024*1024.0))

def DiskFree(disk):
	fsstat = os.statvfs(disk)
	blocks_free = fsstat[statvfs.F_BAVAIL]
	block_size = fsstat[statvfs.F_BSIZE]
	free = block_size * blocks_free
	#print 'B auf',disk,'frei:', free
	return free

def GetUSBStick():
	"""Searches for the path to the USB stick"""
	list = []
	a = os.popen('mount').readlines()
	for i in a:							# parse the answer on 'mount'
		l = int(i.find(' on '))+4
		r = int(i.find(' type '))
		k = i[l:r]
		if os.path.exists(k+'/.is_audio_player'):
			list.append(k)
	return list

def ClearMusicFolder(top):
	"""Deletes all files and subdirectories stored in 'top'"""
	for root, dirs, files in os.walk(top, topdown=False):
		for name in files:
			os.remove(os.path.join(root, name))
		for name in dirs:
			os.rmdir(os.path.join(root, name))

def ReplaceBadChars(string):
	"""Replaces characters which are not allowed in file names (FAT)"""
	#/ \ : * ? " < >
	string = string.replace(':','_')
	string = string.replace('\'','_')
	string = string.replace('*','_')
	string = string.replace('+','_')
	string = string.replace('?','_')
	string = string.replace('\"','_')
	string = string.replace('<','_')
	string = string.replace('>','_')
	string = string.replace("'",'')
	string = string.replace('"','')
	return string

def CopyAlbum(Album, target):
	cnt = 1
	Album.tracks.sort() 		#sort tracks alpabetically

	for track in Album.tracks:
		n = os.path.split(track)
		i = len(n) -1
		name = n[i]
		name = ReplaceBadChars(name)	#
		print cnt,')\t', name

		art = Album.artist.encode('utf-8')
		alb = Album.album.encode('utf-8')
		path = os.path.join(target,art, alb)
		path = ReplaceBadChars(path)
		error = CreateFolder(path)		# make the folder
		if error: continue

		path = os.path.join(path, name)	# add the files name

		#path = os.path.join(target, name)
		try:
			shutil.copyfile(track, path)
		except IOError:
			print "IOError while copying", track
		except OSError:
			print "OSError: Does the file still exist ?"
		cnt += 1
	print '\n'

	#save m3u-list
	art = Album.artist.encode('utf-8')
	alb = Album.album.encode('utf-8')
	path = os.path.join(target,art, alb,art+'_'+alb + '.m3u')
	path = ReplaceBadChars(path)
	Album.savem3u(path)

	#search for cover
	art = Album.artist.encode('utf-8')
	alb = Album.album.encode('utf-8')
	path = os.path.join(target,art, alb)
	Album.findCover(path)

	return 0

def testASCII(text):
	s2 = ""
	for i in text:
		if ord(i) < 128: s2 += i

	return len(s2) == len(text)

def DB2HTML(List):
	"""This function converts the list of albums to a html page."""
	amazon = "http://www.amazon.de/s/ref=nb_ss_w?__mk_de_DE=%C5M%C5Z%D5%D1&url=search-alias%3Daps&field-keywords=%keywords%&Go.x=10&Go.y=10&Go=Go"
	artists = []
	for i in List.db:
		temptitle = Title(i.artist)
		if temptitle == None: continue
		t = temptitle.encode('utf-8')

		if t not in artists:
			artists.append(t)

	artists.sort()

	acount = len(artists)

	t = []
	t.append("<table border=\"0\" width=\"90%\" height=\"90%\"><tr><td valign=\"top\">")
	c = 0
	for i in artists:
		print i
		#if not testASCII(i): print "warning, non ASCII chars"
		c += 1

		albums=[]
		imgpaths={}
		albpaths={}
		for j in List.db:
			try:
				title = Title(j.artist).encode('utf-8')
			except: title = None
			if title == i:
				#print j.album
				try:
					alb = Title(j.album).encode('utf-8')
				except: alb = None

				if alb :
					albums.append(alb)
					albpaths[alb] = j.path
					imgpaths[alb] = os.path.join(j.path, settings.COVER_IMAGE_NAME)

		albums.sort()
		temp = """
		<a href="javascript:ToggleVisibility(\'%s\')" STYLE="text-decoration:none;"><b>%s</b></a><br>\n
		<div ID="%s" STYLE="display:none;">""" % ('id'+str(c),i,'id'+str(c))
		#temp += '	<ul>\n'
		temp += '	<table>\n'
		for j in albums:
			#temp += '		<li>%s</li>\n' % j
			myamazon = amazon.replace("%keywords%", i + ' ' + j)

			#if os.path.exists(imgpaths[j]):
				#temp += '		<li><a href="%s"><img src="%s" height=60 width=60 border=0 align=center>&nbsp;%s</a></li>\n' % (myamazon,imgpaths[j], j)
			#else:
				#temp += '		<li><a href="%s">%s</a></li>\n' % (myamazon,j)
			if os.path.exists(imgpaths[j]):
				temp += '		<tr valign="center"><td witdh=60><a href="%s"><img src="%s" height=60 width=60 border=0 align=center></a></td><td><a href="%s">%s</a></td></tr>\n' % (myamazon,imgpaths[j],albpaths[j],j)
			else:
				temp += '		<tr valign="center"><td witdh=60><a href="%s">[a]</a></td><td><a href="%s">%s</a></td></tr>\n' % (myamazon,albpaths[j],j)


		#temp += '	</ul>\n	</div>\n'
		temp += '	</table>\n	</div>\n'

		if c == int(acount/4) or c == int(acount/2) or c == int(3*acount/4):
			temp += '</td><td valign="top">'
		elif c == int(acount):
			temp += '</td></table>'
		#temp = temp.encode('utf-8')
		t.append(temp)

	t.insert(0,"""
	<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	</head>\n\n
	<body>
	<script type="text/javascript">
	function ToggleVisibility(id) {
    if (document.getElementById(id).style.display == "none") {
                 document.getElementById(id).style.display = "block";
               }
    else
    if (document.getElementById(id).style.display == "block") {
                 document.getElementById(id).style.display = "none";
                   }
	}
	</script>
	""")
	t.append("</body>\n</html>")
	#save this list to a file
	f = open('db.html', 'w')
	f.writelines(t)
	f.close()

	os.popen('firefox db.html &')

############################ MAIN PROGRAM #############################

if '-a' in sys.argv: select_automatically = True
else: select_automatically = False

if '-?' in sys.argv:
	print """usage:
	 When you run this script for the first time, then
	 you'll have to scan your mp3-files. This is done by typing
	 ./pyran -s <path_to_your_mp3_folder>
	 You'll have to repeat this every time there are changes in
	 your mp3-folder

	 Next you can choose if you want to confirm the albums or if
	 they shall be copied automatically to your device.
	 This is done with the option
	 ./pyran -a

	 Your music device is detected with the command mount. In
	 order to find it correctly. There must be the file
	 .is_audio_player
	 on your device in the main folder. By the way rhythmbox
	 needs this file , too.
	 If it not exists the script will create a folder 'music'
	 on your device and fills it with music until there's no
	 more free space. If the folder already exists it will be
	 deleted. So be careful. """
	sys.exit(1)

List = TAlbumList('')
if os.path.exists('db.pickle'):
	try:
		file=open("db.pickle")
		List=pickle.load(file)
		file.close()
	except:
		print "Error reading the file db.pickle"

	#HTML output
	if ('-l' in sys.argv):
		DB2HTML(List)
		sys.exit()

verbose = ('-v' in sys.argv)

# Scan the folder
if ('-s' in sys.argv):

	dbpath = ''
	for i in range(len(sys.argv)):
		if (sys.argv[i] == '-s') and (i + 1 < len(sys.argv)):
			dbpath = sys.argv[i+1]

	if not os.path.exists(dbpath):
		print dbpath,"does not exist."
		sys.exit(1)
	if List.path != dbpath:			# only create a new list if the paths are different
		List = TAlbumList(dbpath)
	else: # else an old list exists and we should clean up a bit
		CleanDB(List, verbose)
	ScanDB(List, verbose)		# scan the database

List.info() #  print the info

Sticks = GetUSBStick()

if len(Sticks) == 0 :
	print "No music device found."
	dumpList(List)
	sys.exit(1)
elif len(Sticks)  == 1 :
	USBDevice = Sticks[0]

elif len(Sticks)  > 1 :
	for s in range(len(Sticks)):
		print s+1,':',Sticks[s]
	a = raw_input('Your choice:')
	try:
		int(a)
	except:
		print "a is not an integer"
		sys.exit(1)
	else:
		USBDevice = Sticks[a]

print 'using device :',USBDevice
print "free space on", USBDevice,':',DiskFree(USBDevice)

USBDevice += '/music/'

if not os.path.exists(USBDevice):
	a = raw_input('Subfolder `music` does not exist. Create ? (y/n) ')
	if a == 'y' or a == 'Y':
		os.mkdir(USBDevice)
else:
	ClearMusicFolder(USBDevice)

freespace = DiskFree(USBDevice)
user_abort = False
while freespace > settings.leave_free_space and not user_abort:
	rand = RandomAlbum(List, freespace)
	if rand == None:
		print "no more albums left"
		print "free space on", USBDevice,':',DiskFree(USBDevice)
		dumpList(List)
		sys.exit(1)
	else:
		if select_automatically:
			print rand.artist,'-', rand.album
			CopyAlbum(rand, USBDevice)
		else:
			str = (rand.artist + ' - ' + rand.album).encode('utf-8')
			a = raw_input(str + ' ?(y/n/N/q)')
			if (a =='y') or (a=='Y'):
				CopyAlbum(rand, USBDevice)
			elif (a=='N'):	# never show again
				rand.neveragain = True
			elif (a=='q'):
				user_abort = True
	freespace = DiskFree(USBDevice)

print "free space on", USBDevice,':',DiskFree(USBDevice)
dumpList(List)
															#update the Database

# select one Album randomly
#test = RandomAlbum(List)
#print test.artist, test.album
#print test.tracks
#print "size:",test.getsize()

#FolderSize('/media/PHONE CARD/music')
#DiskFree('/media/PHONE CARD/music')

